/*	talmud - The TALMUD roguelike
 * Copyright (C) 2012, talmud contributors
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of Version 2 of the GNU General Public License as published
 *  by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE. See Version 2 of the GNU General Public
 *  License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "monstermanage.h"
#include "monster.h"
#include "race.h"
#include "talmud.h"
#include "rand.h"
#include "draw.h"

monster_t *mon = NULL;
uint16_t moncount = 0;
uint16_t monmax = 0;

static void InitMon(monster_t *tmon)
{
	tmon->cintrins = I_NONE;
	tmon->resists = I_NONE;
	tmon->flags = F_NONE;
	tmon->race = R_HUMAN;
	tmon->hp = 1;
	tmon->exhaustion = 0;
	tmon->x = 0;
	tmon->y = 0;
	tmon->dlvl = 1;
	tmon->branch = B_DUNGEONS;
	tmon->speedcounter = 0;
	tmon->invcount = 0;
}

static void BuildMon(monster_t *tmon, uint8_t race)
{
	tmon->resists = RACEDEFS[race].resist;
	tmon->race = race;
	tmon->hp = RACEDEFS[race].maxhp;
}

void MonGen(uint8_t difficulty)
{
	uint8_t x;
	uint8_t y;
	uint8_t result;

	result = FindMonSpot(&x, &y);
	if (result)
	{
#ifdef _WIZARD
		AddMsg("Couldn't find a floor tile on the map.");
#endif
		return;
	}

	MonGenNear(difficulty, x, y);
}

void MonGenNear(uint8_t difficulty, uint8_t x, uint8_t y)
{
	uint16_t total;
	uint8_t *races;
	uint8_t *slots;
	uint16_t slotcount;
	uint8_t idx;
	uint16_t roll;
	uint8_t count;
	uint8_t race;
	int8_t dx;
	int8_t dy;
	uint8_t result;

	slots = (uint8_t*)malloc(R_COUNT);
	races = (uint8_t*)malloc(R_COUNT);
	total = 0;
	slotcount = 0;
	for (idx = 0; idx < R_COUNT; idx++)
	{
		if (RACEDEFS[idx].freq <= F_NEVER)
		{
			continue;
		}
		else if (RACEDEFS[idx].diff > difficulty)
		{
			continue;
		}
		races[slotcount] = idx;
		slots[slotcount] = RACEDEFS[idx].freq;
		total += RACEDEFS[idx].freq;
		slotcount++;
	}

	roll = rn1(total);
	for (idx = 0; idx < slotcount; idx++)
	{
		if (roll < slots[idx])
		{
			break;
		}
		roll -= slots[idx];
	}
	race = races[idx];
	free(slots);
	free(races);
	count = 1 + rn3(RACEDEFS[race].grp - 1);

	if (moncount + count > monmax)
	{
		if (monmax >= MONMAX)
		{
#ifdef _WIZARD
			AddMsg("The world feels crowded.");
#endif
			return;
		}
		monmax = monmax + 20;
		mon = (monster_t*)realloc(mon, sizeof(monster_t) * monmax);
		if (!mon)
		{
			AddMsg("Your memory fades... You die.");
			GameOver();
			return;
		}
	}

	for (idx = 0; idx < count; idx++)
	{
		result = FindMonSpotNear(x, y, &dx, &dy);
		if (result)
		{
#ifdef _WIZARD
			AddMsg("Couldn't find a floor tile near my original spot.");
#endif
			continue;
		}
		InitMon(&mon[moncount]);
		BuildMon(&mon[moncount], race);
		if (count == 1 && !rn1(5))
		{
			mon[moncount].flags |= F_ASLEEP;
		}
		mon[moncount].x = x + dx;
		mon[moncount].y = y + dy;
		mon[moncount].dlvl = plyrmon.dlvl;
		mon[moncount].branch = plyrmon.branch;
		moncount++;
	}
}

void GenSpecMon(uint8_t monid)
{
	uint8_t result;
	uint8_t x;
	uint8_t y;

	InitMon(&mon[moncount]);

	switch (monid)
	{
		case MON_BOSS:
			BuildMon(&mon[moncount], R_THEACOLYTE);
		default:
			return;
	}

	result = FindMonSpot(&x, &y);
	if (result)
	{
#ifdef _WIZARD
		AddMsg("Couldn't find a floor tile on the map.");
#endif
		return;
	}

	mon[moncount].x = x;
	mon[moncount].y = y;
	mon[moncount].dlvl = plyrmon.dlvl;
	mon[moncount].branch = plyrmon.branch;
	moncount++;
}

uint16_t GetMonAt(uint8_t x, uint8_t y)
{
	uint16_t idx;

	for (idx = 0; idx < moncount; idx++)
	{
		if (!mononlvl(idx))
		{
			continue;
		}
		else if (mon[idx].x != x || mon[idx].y != y)
		{
			continue;
		}
		return idx;
	}

	return MON_NULL;
}

uint8_t MonDmg(uint16_t monidx, uint8_t dmg)
{
	if (mon[monidx].hp > dmg)
	{
		mon[monidx].hp -= dmg;
		return 0;
	}
	mon[monidx].hp = 0;
	return 1;
}

static void MonAtks(uint16_t monidx)
{
	uint8_t atkidx;
	uint8_t hdidx;
	uint8_t atktype;
	int8_t tohit;
	uint8_t hdcount;
	uint8_t hd;
	uint8_t race = mon[monidx].race;
	int8_t roll;
	uint8_t dmg;
	uint8_t killed;

	for (atkidx = 0; atkidx < RACEDEFS[race].atkcount; atkidx++)
	{
		atktype = RACEDEFS[race].atktype[atkidx];
		tohit = RACEDEFS[race].tohit[atkidx];
		hdcount = RACEDEFS[race].hdcount[atkidx];
		hd = RACEDEFS[race].hd[atkidx];

		roll = rn1(20) + 1;
		if (roll + tohit < 0)
		{
			roll = 1;
		}
		else
		{
			roll += tohit;
		}
		if (roll < 6 + plyreffac)
		{
			AddMsg("%s misses.", capword(monterm(monidx)));
			continue;
		}
		switch (atktype)
		{
			case A_HIT:
				AddMsg("%s hits.", capword(monterm(monidx)));
				break;
			case A_KICK:
				AddMsg("%s kicks.", capword(monterm(monidx)));
				break;
			case A_BITE:
				AddMsg("%s bites.", capword(monterm(monidx)));
				break;
			case A_GAZE:
				AddMsg("%s stares at you.", capword(monterm(monidx)));
				break;
			default:
				AddMsg("%s hits you with an unimplemented feature! You die.", capword(monterm(monidx)));
				GameOver();
				break;
		}
		dmg = 0;
		for (hdidx = 0; hdidx < hdcount; hdidx++)
		{
			dmg += rn1(hd) + 1;
		}
		if (plyracbns > dmg)
		{
			dmg = 1;
		}
		else
		{
			dmg -= plyracbns;
		}
		killed = PlyrDmg(dmg);
		if (killed)
		{
			return;
		}
	}
}

static void MonDTrap(uint16_t monidx)
{
	uint8_t roll;

	if (dexposed(mon[monidx].x, mon[monidx].y) == V_CANSEE)
	{
		AddMsg("You hear a click.");
	}
	else
	{
		AddMsg("You hear a distant click.");
	}
	roll = rn1(3);
	if (!roll)
	{
		return;
	}
	if (dexposed(mon[monidx].x, mon[monidx].y) == V_CANSEE)
	{
		AddMsg("%s is hit by a dart.", capword(monterm(monidx)));
	}
	roll = 1 + rn1(3);
	MonDmg(monidx, roll);
}

static void MonMoves(uint16_t monidx, int8_t dx, int8_t dy)
{
	uint8_t px;
	uint8_t py;
	uint8_t tile;
	uint16_t atkmon;

	px = mon[monidx].x + dx;
	py = mon[monidx].y + dy;
	atkmon = GetMonAt(px, py);
	if (atkmon != MON_NULL)
	{
		return;
	}
	if (px == plyrmon.x && py == plyrmon.y)
	{
		MonAtks(monidx);
		return;
	}
	tile = dtile(px, py);
	if (impassable(tile))
	{
		return;
	}
	mon[monidx].x = px;
	mon[monidx].y = py;
	switch (tile)
	{
		case T_DTRAP:
			MonDTrap(monidx);
			break;
	}
}

static void MonWanders(uint16_t monidx)
{
	MonMoves(monidx, rn1(3) - 1, rn1(3) - 1);
}

static void MonAI(uint16_t monidx)
{
	int8_t dx;
	int8_t dy;
	uint8_t slope;
	uint8_t ax;
	uint8_t ay;
	int8_t sx;
	int8_t sy;

	if (PlyrDead())
	{
		return;
	}

	if (mon[monidx].flags & F_PEACEFUL)
	{
		MonWanders(monidx);
		return;
	}
	dx = plyrmon.x - mon[monidx].x;
	dy = plyrmon.y - mon[monidx].y;
	if (dx * dx + dy * dy > MONR2)
	{
		MonWanders(monidx);
		return;
	}
	if (!monlos(monidx, plyrmon.x, plyrmon.y))
	{
		MonWanders(monidx);
		return;
	}

	ax = abs(dx) << 1;
	ay = abs(dy) << 1;
	sx = delta(dx);
	sy = delta(dy);

	if (dx >= dy)
	{
		slope = ax - (ay >> 1);
		MonMoves(monidx, delta(dx), delta(slope * sy));
	}
	else
	{
		slope = ay - (ax >> 1);
		MonMoves(monidx, delta(slope * sx), delta(dy));
	}
}

void UpdateMon(void)
{
	uint16_t idx;
	uint8_t dlvlmon;

	dlvlmon = 0;
	for (idx = 0; idx < moncount; idx++)
	{
		if (!mononlvl(idx))
		{
			continue;
		}
		dlvlmon++;
		mon[idx].speedcounter += RACEDEFS[mon[idx].race].speed;
		while (mon[idx].speedcounter >= TURNSPEED)
		{
			mon[idx].speedcounter -= TURNSPEED;
			if (mon[idx].flags & F_ASLEEP)
			{
				continue;
			}
			MonAI(idx);
		}
	}

	if (dlvlmon < 4 + rn3(4) && !rn1(80))
	{
#ifdef _WIZARD
		AddMsg("You hear the sound of eggshells breaking.");
#endif
		MonGen((plyr.xlvl + plyrmon.dlvl) >> 1);
	}
}

void DrawMon(void)
{
	uint16_t idx;

	for (idx = 0; idx < moncount; idx++)
	{
		if (!mononlvl(idx))
		{
			continue;
		}
		else if (dexposed(mon[idx].x, mon[idx].y) != V_CANSEE)
		{
			continue;
		}
		SetColor(RACEDEFS[mon[idx].race].attr);
		DrawChar(mon[idx].x, DLVL_BASEY + mon[idx].y, RACEDEFS[mon[idx].race].rep);
	}
}

void CullDeadMon(void)
{
	uint16_t idx;

	for (idx = 0; idx < moncount; idx++)
	{
		if (mon[idx].hp <= 0)
		{
			memcpy(&mon[idx], &mon[moncount - 1], sizeof(monster_t));
			moncount--;
			idx--;
			continue;
		}
	}
}
